﻿#region License Information
// Copyright (c) 2005 - 2008, Allan Spartacus Mangune (allan@owlpal.com)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list 
//   of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice, this list of 
//   conditions and the following disclaimer in the documentation and/or other materials provided 
//   with the distribution.
//
// * Neither the name of Owlpal nor the names of its contributors may be used to endorse or 
//   promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
// FITNESS FOR AllowHtmlInComment PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
#endregion

using System;
using System.Web.Security;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration.Provider;
using System.Web.Hosting;
using System.Xml;
using System.Security.Permissions;
using System.Web;

namespace Owlpal.Models
{
    public class XmlRoleProvider : RoleProvider
    {
        #region Members
        private Dictionary<string, string[]> usersAndRoles =
            new Dictionary<string, string[]>(16,
            StringComparer.InvariantCultureIgnoreCase);

        private Dictionary<string, string[]> rolesAndUsers =
            new Dictionary<string, string[]>(16,
            StringComparer.InvariantCultureIgnoreCase);

        private string xmlFileName;
        #endregion

        #region Properties
        public override string ApplicationName
        {
            get { throw new NotSupportedException(); }
            set { throw new NotSupportedException(); }
        }
        #endregion
        
        #region Members
        public override void Initialize(string name,
            NameValueCollection config)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            if (String.IsNullOrEmpty(name))
                name = "XmlRoleProvider";

            if (string.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "XML role provider");
            }

            base.Initialize(name, config);

            string path = config["xmlFileName"];

            if (String.IsNullOrEmpty(path))
                path = "~/App_Data/Roles.xml";

            if (!VirtualPathUtility.IsAppRelative(path))
                throw new ArgumentException
                    ("xmlFileName must be app-relative");

            string fullyQualifiedPath = VirtualPathUtility.Combine
                (VirtualPathUtility.AppendTrailingSlash
                (HttpRuntime.AppDomainAppVirtualPath), path);

            xmlFileName = HostingEnvironment.MapPath(fullyQualifiedPath);
            config.Remove("xmlFileName");

            FileIOPermission permission =
                new FileIOPermission(FileIOPermissionAccess.Read,
                xmlFileName);
            permission.Demand();

            if (config.Count > 0)
            {
                string attr = config.GetKey(0);
                if (!String.IsNullOrEmpty(attr))
                    throw new ProviderException
                        ("Unrecognized attribute: " + attr);
            }

            ReadRoleDataStore();
        }

        public override bool IsUserInRole(string username, string roleName)
        {
            if (username == null || roleName == null)
                throw new ArgumentNullException();
            if (username == String.Empty || roleName == string.Empty)
                throw new ArgumentException();

            if (!usersAndRoles.ContainsKey(username))
                throw new ProviderException("Invalid user name");
            if (!rolesAndUsers.ContainsKey(roleName))
                throw new ProviderException("Invalid role name");

            string[] roles = usersAndRoles[username];
            foreach (string role in roles)
            {
                if (String.Compare(role, roleName, true) == 0)
                    return true;
            }

            return false;
        }

        public override string[] GetRolesForUser(string username)
        {
            if (username == null)
                throw new ArgumentNullException();
            if (username == string.Empty)
                throw new ArgumentException();

            string[] roles;
            if (!usersAndRoles.TryGetValue(username, out roles))
                throw new ProviderException("Invalid user name");

            return roles;
        }

        public override string[] GetUsersInRole(string roleName)
        {
            if (roleName == null)
                throw new ArgumentNullException();
            if (roleName == string.Empty)
                throw new ArgumentException();

            string[] users;
            if (!rolesAndUsers.TryGetValue(roleName, out users))
                throw new ProviderException("Invalid role name");

            return users;
        }

        public override string[] GetAllRoles()
        {
            int i = 0;
            string[] roles = new string[rolesAndUsers.Count];
            foreach (KeyValuePair<string, string[]> pair in rolesAndUsers)
                roles[i++] = pair.Key;
            return roles;
        }

        public override bool RoleExists(string roleName)
        {
            if (roleName == null)
                throw new ArgumentNullException();
            if (roleName == string.Empty)
                throw new ArgumentException();

            return rolesAndUsers.ContainsKey(roleName);
        }

        public override void CreateRole(string roleName)
        {
            throw new NotSupportedException();
        }

        public override bool DeleteRole(string roleName,
            bool throwOnPopulatedRole)
        {
            throw new NotSupportedException();
        }

        public override void AddUsersToRoles(string[] usernames,
            string[] roleNames)
        {
            throw new NotSupportedException();
        }

        public override string[] FindUsersInRole(string roleName,
            string usernameToMatch)
        {
            throw new NotSupportedException();
        }

        public override void RemoveUsersFromRoles(string[] usernames,
            string[] roleNames)
        {
            throw new NotSupportedException();
        }

        private void ReadRoleDataStore()
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(xmlFileName);
            XmlNodeList nodes = doc.GetElementsByTagName("User");

            foreach (XmlNode node in nodes)
            {
                if (node["UserName"] == null)
                    throw new ProviderException
                        ("Missing UserName element");

                string user = node["UserName"].InnerText;
                if (String.IsNullOrEmpty(user))
                    throw new ProviderException("Empty UserName element");

                if (node["Roles"] == null ||
                    String.IsNullOrEmpty(node["Roles"].InnerText))
                    usersAndRoles.Add(user, new string[0]);
                else
                {
                    string[] roles = node["Roles"].InnerText.Split(',');
                    usersAndRoles.Add(user, roles);

                    foreach (string role in roles)
                    {
                        string[] users1;

                        if (rolesAndUsers.TryGetValue(role, out users1))
                        {
                            string[] users2 =
                                new string[users1.Length + 1];
                            users1.CopyTo(users2, 0);
                            users2[users1.Length] = user;
                            rolesAndUsers.Remove(role);
                            rolesAndUsers.Add(role, users2);
                        }
                        else
                            rolesAndUsers.Add(role,
                                new string[] { user });
                    }
                }
            }
        }
        #endregion
    }
}